Part 1: stats
#install required package
if (!require("BiocManager", quietly = TRUE))
install.packages("BiocManager")
BiocManager::install("FlowSOM") #for flowsom clustering
BiocManager::install("flowCore") #for flowsom clustering
BiocManager::install("cluster") #for silhouette score
BiocManager::install("fpc") #for ch index
BiocManager::install('clv') #for dbi
BiocManager::install('Seurat') #for pca and flowsom visualization
library(FlowSOM)
library(flowCore)
library(cluster)
library(fpc)
library(clv)
library(Seurat)
library(dplyr)
rm(list=ls())
#input file path, change if needed
fileName <-"/Users/rhalenathomas/Documents/Data/FlowCytometry/PhenoID/Analysis/9MBO/prepro_outsjan20-9000cells/prepro_outsflowset.csv"
# note: current matrix sample ID have cell index # attached.
df <- read.csv(fileName)
head(df)
print(dim(df)) # this is specific df has 73578 cells
# the preprocessing output csv needs to be cleaned - it contains live dead, FSC, SSC and the sample column
df2 <- df %>% select(-c("Live.Dead",FSC,SSC,X,Batch,cell))
m <- as.matrix(df2) # flowset takes ina matrix not df
#m <- om[1:3000,] #subset (n=3000), omit this to test the whole file - I didn't subset here but too 9000 or max cells from each file before
#try scaling
# SOM = self organizing map, MST = minimal spanning tree
# if reading in a csv convert to flowset
frame <- new("flowFrame", exprs = m) #convert input to flowframe
fs <- ReadInput(frame) #convert flowframe to flowsom object
# fs <- BuildSOM(fs,colsToUse=(-1)) #-1 because we are not using "X" column to build SOM
fs <- BuildSOM(fs) # build flowSOM object, no need for -1 because I cleaned the df about before making flowset
fs <- BuildMST(fs) # build minimum spanning tree
# BuildMST(flowSOM object generated by buildSOM)
# save the FlowSOM MST object to try and avoid memory error
saveRDS(fs,"/Users/rhalenathomas/Documents/Data/FlowCytometry/PhenoID/Analysis/9MBO/prepro_outsjan20-9000cells/FlowSOMin.rds")
Saved the flowset object I created and load the object here - but I didn’t save the filtered df converted to matrix, which is needed for the stats below
fs <- readRDS("/Users/rhalenathomas/Documents/Data/FlowCytometry/PhenoID/Analysis/9MBO/prepro_outsjan20-9000cells/FlowSOMin.rds")
# note to Shuming - we should be able to go directly from flowset in preprocessing into this flowset but then the 9 samples (or however many will be in separate frames)
# I don't know where the actual values are located
# skip
#based on flowsom, the optimal k number of clusters = 5 for the sample (n=3000)
metacl <- MetaClustering(fs$map$codes,
"metaClustering_consensus",
max = 15)
unique(metacl[fs$map$mapping[, 1]])
#a function that calculate 3 stats for k number of clustering
# the silhouette stat takes too much memory and we will not run this for now.
# Shuming I need this function to take in a list of clustering indexes and an expression matrix
stats <- function(krange){
# si_li <-list()
ch_li <-list()
dbi_li <-list()
for (k in krange) {
#flowsom clustering, try each k in krange
metaClustering <- (metaClustering_consensus(fs$map$codes,k = k,seed=42)) # here we are clustering within the loop
# @Shuming we need to change this so that the loop can pull the results of clustering out of another data object
# how am I going to test the Seurat or Phenograph clustering?
#calculate silhouette score
# cluster index, distance matrix of pairs --- maybe calculate in advance
#si <- silhouette(metaClustering[fs$map$mapping[, 1]],dist(m),)
#si_li[k] <- mean(si[, 3])
#calculate Calinski-Harabasz index
# calinhara(x,clustering,cn=max(clustering))
# where x is a matrix or dataframe
ch = calinhara(m,metaClustering[fs$map$mapping[, 1]],cn=max(metaClustering[fs$map$mapping[,1]]))
ch_li[k] <- ch
#calculate Davies–Bouldin index
dbi = clv.Davies.Bouldin(cls.scatt.data(
m,
metaClustering[fs$map$mapping[, 1]]),
intracls = "average",
intercls = "average"
)
dbi_li[k] <- dbi[1]
}
return(list(ch_li, dbi_li))
}
# run the function to get the stats
krange <- 3:15 #range of number of clusters
li = stats(krange) # doesn't contain silhouette
# list of 2
#silhouette score: ranges from -1 to 1
#-1: bad clusters 0: neutral, indifferent 1: good clusters
#plot(krange, type='b', li[[1]][krange], xlab='Number of clusters', ylab='Average Silhouette Scores', frame=TRUE)
#Calinski-Harabasz index:
# the highest value is the optimal number of clusters
plot(krange, type='b', li[[1]][krange], xlab='Number of clusters', ylab='Calinski-Harabasz index', frame=TRUE)
#Davies–Bouldin index: minimum score is zero
#the lowest value is the optimal number of clusters
plot(krange, type='b', li[[2]][krange], xlab='Number of clusters', ylab='Davies–Bouldin index', frame=TRUE)
The Calinski-Harabasz index show that K=8 is best number of clusters. But the Davies–Bouldin index shows lower is better. It is okay up until 9.
Try statistics on other clustering.
ch.pheno.k.50 = calinhara(m,df$phenograph,length((unique(df$phenograph))))
Error in if (cln[i] < 2) cclx <- 0 : argument is of length zero
David Bouldin index for Phenograph
make this calculations on the Seurat clustering
# read in the seurat object with the clustering values
seu <- readRDS("/Users/rhalenathomas/Documents/Data/FlowCytometry/PhenoID/Analysis/9MBO/prepro_outsjan20-9000cells/SeuratfromFlowsomPheno.rds" )
CH index and DBI from Seurat (Louvain) try one resolution first
dbi.louv.res.1 <- clv.Davies.Bouldin(cls.scatt.data(
m,
seu@meta.data$RNA_snn_res.1),
intracls = "average",
intercls = "average"
)
Error in cls.id.vect.validity(clust, "clust") :
Bad usage: input 'clust' should be vector type.
NA
Calculate all CHI for Louvain
ch_louv <-list()
l.range <- c("RNA_snn_res.0.1","RNA_snn_res.0.25","RNA_snn_res.0.5","RNA_snn_res.0.75","RNA_snn_res.1")
resolution = c(0.1,0.25,0.5,0.75,1)
prefix = "RNA_snn_res."
# loop doesn't work because I can't index the seurat resolutions
ch = calinhara(m,seu@meta.data$RNA_snn_res.0.1,length((unique(seu@meta.data$RNA_snn_res.0.1))))
ch_louv["RNA_snn_res.0.1"] <- ch
ch = calinhara(m,seu@meta.data$RNA_snn_res.0.25,length((unique(seu@meta.data$RNA_snn_res.0.25))))
ch_louv["RNA_snn_res.0.25"] <- ch
ch = calinhara(m,seu@meta.data$RNA_snn_res.0.5,length((unique(seu@meta.data$RNA_snn_res.0.5))))
ch_louv["RNA_snn_res.0.5"] <- ch
ch = calinhara(m,seu@meta.data$RNA_snn_res.0.75,length((unique(seu@meta.data$RNA_snn_res.0.75))))
ch_louv["RNA_snn_res.0.75"] <- ch
ch = calinhara(m,seu@meta.data$RNA_snn_res.1,length((unique(seu@meta.data$RNA_snn_res.1))))
ch_louv["RNA_snn_res.1"] <- ch
Plot the CHI from Louvain clusters

Make a table with the CHI outputs
df.chi <- as.data.frame(Method, CHI.value)
Warning in as.data.frame.vector(x, ..., nm = nm) :
'row.names' is not a character vector of length 7 -- omitting it. Will be an error!
Make another plot
#library(ggplot2)
ggplot(df.chi, aes(x= Method, y= CHI.value)) + geom_point() +
theme(axis.text.x = element_text(angle = 90))

Part 2: plot
#transpose the csv so that seurat object has the right column and row
# the flow intensity values will be input as RNA expression
tm <- t(df2)
rownames(tm) <- colnames(df2)
colnames(tm) <- rownames(df2)
k <- 10 #change number of cluster here
k <- 8 # best number of clusters according to CH index
metaClustering <- (metaClustering_consensus(fs$map$codes,k = k,seed=42)) # flowSOM clustering?
seu <- CreateSeuratObject(tm) # create a seurat object
# check the seurat object by plotting some features
print(colnames(df2))
allAB <- colnames(df2)
VlnPlot(seu,features = c("CD56","AQP4")) # eerror in plot
DotPlot(seu, features = c("CD56","AQP4","CD24","GLAST","CD140a","CD29","CD184","CD71","O4","HepaCAM","CD133"))
# scale the data
seu <- ScaleData(seu)
DotPlot(seu, features = c("CD56","AQP4","CD24","GLAST","CD140a","CD29","CD184","CD71","O4","HepaCAM","CD133"))
DotPlot(seu, features = allAB)
#do i need to normalize?
# seu <- NormalizeData(seu, normalization.method = "LogNormalize", scale.factor = 10000)
# create a UMAP
# to do so PCA must be run first
seu <- FindVariableFeatures(seu) # needed for PCA or select all features
#dimentionality reduction methods: pca and umap
seu <-RunPCA(seu,seed.use = 42) # will use variable features by default
seu <- RunPCA(seu, features = allAB)
print(seu[["pca"]], dims = 1:5, nfeatures = 5) # tells you what AB are contributing to the
seu <- RunUMAP(seu,features = allAB)
# Add the clustering data to the seurat object
seu <- AddMetaData(object=seu, metadata=metaClustering[fs$map$mapping[,1]], col.name = 'flowSOM.k.8')
DimPlot(seu, group.by = "flowSOM", reduction = "pca")
DimPlot(seu, group.by = "flowSOM", reduction = "umap") # from normal PCA used as umap input it is mostly on blob, I get the same shape when using all AB features as PCA input
DotPlot (seu, features = allAB, group.by = "flowSOM")
#allow for color labeling
Idents(seu) <- "flowSOM"
FLowSOM clustering visually appear to be terrible.
I’ll just try the seurat clustering
# cluster using Louvain clustering
# using the square root of the number of cells for K might work better
#sqrt(73578) = 271.25
seu <- FindNeighbors(seu, dims = 1:10, k = 271)
seu <- FindClusters(seu, resolution = c(0.1,0.25,0.5,0.75,1))
clustree(seu, prefix = "RNA_snn_res.") + theme(legend.position = "bottom")
# this is very slow - do not run again in notebook
library(clustree)
clustree(seu, prefix = "RNA_snn_res.") + theme(legend.position = "bottom")

# from Clustree 0.5 looks like the best resolution
# c(0.1,0.25,0.5,0.75,1)
resolutions = c("RNA_snn_res.0.1","RNA_snn_res.0.25","RNA_snn_res.0.5","RNA_snn_res.0.75","RNA_snn_res.1")
for (res in resolutions){
print(DimPlot(seu, reduction = "umap", repel = TRUE, label = TRUE, group.by = res))
}





NA
NA
The clusters are not well separated, but better than with flowSOM (visually) Lets see how a dotplot looks
DotPlot(seu, group.by = "RNA_snn_res.0.5", features = allAB, scale = TRUE) # why is the intensity ploted?
# the data must not be working correctly
# maybe the object is not really correct at all
FeatureScatter(seu, feature1 = "CD56",feature2 = "CD24")
FeatureScatter(seu, feature1 = "CD29",feature2 = "CD184")
# maybe the data needs to be normalized
seu <- NormalizeData(seu)
DoHeatmap(seu, features = allAB, group.by = "RNA_snn_res.0.5")
# the groups look okay by heatmap. I'll try the different resolutions
# c(0.1,0.25,0.5,0.75,1)
resolutions = c("RNA_snn_res.0.1","RNA_snn_res.0.25","RNA_snn_res.0.5","RNA_snn_res.0.75","RNA_snn_res.1")
for (res in resolutions){
print(DoHeatmap(seu, features = allAB, group.by = res))
}





# try the dotplot again
# c(0.1,0.25,0.5,0.75,1)
resolutions = c("RNA_snn_res.0.1","RNA_snn_res.0.25","RNA_snn_res.0.5","RNA_snn_res.0.75","RNA_snn_res.1")
for (res in resolutions){
print(DotPlot(seu, features = allAB, group.by = res, cols = c("blue","red")))
}
# what is happening?
Try feature maps
for (AB in allAB){
print(FeaturePlot(seu, features = AB),slot = 'scale.data',min.cutoff = 'q10', max.cutoff ='90')
}













# autothreshold isn't great - I set quartiles
#FeaturePlot(seu, features = "O4", min.cutoff = 1, max.cutoff = 25)
#FeaturePlot(seu, features = "O4", slot = 'scale.data',min.cutoff = 0.1, max.cutoff = 25)
#FeaturePlot(seu, features = "O4", slot = 'scale.data',min.cutoff = 'q5', max.cutoff ='99')
Try ridge plots
RidgePlot(seu, features = c("CD56","CD24"), ncol = 2) # missing values???
VlnPlot(seu, features = c("CD56","CD24"), ncol = 2) # also missing values???
I’ll save the seurat object and see about trying to cluster with phenograph. I believe I have read in the unalligned not normalized data
saveRDS(seu, "/Users/rhalenathomas/Documents/Data/FlowCytometry/PhenoID/Analysis/9MBO/prepro_outsjan20-9000cells/SeuratfromFlowsom.rds")
Phenograph clustering uses Jaccard coefficient between neruest neigbors sets and then id communities by louvain
# install
if(!require(devtools)){
install.packages("devtools") # If not already installed
}
devtools::install_github("JinmiaoChenLab/Rphenograph")
library(Rphenograph)
LS0tCnRpdGxlOiAic3RhdHM6IHNpbGhvdWV0dGUgc2NvcmUsIGNoLCBkYmkiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KClBhcnQgMTogc3RhdHMKYGBge3J9CgojaW5zdGFsbCByZXF1aXJlZCBwYWNrYWdlCmlmICghcmVxdWlyZSgiQmlvY01hbmFnZXIiLCBxdWlldGx5ID0gVFJVRSkpCiAgICBpbnN0YWxsLnBhY2thZ2VzKCJCaW9jTWFuYWdlciIpCgpCaW9jTWFuYWdlcjo6aW5zdGFsbCgiRmxvd1NPTSIpICNmb3IgZmxvd3NvbSBjbHVzdGVyaW5nCkJpb2NNYW5hZ2VyOjppbnN0YWxsKCJmbG93Q29yZSIpICNmb3IgZmxvd3NvbSBjbHVzdGVyaW5nCkJpb2NNYW5hZ2VyOjppbnN0YWxsKCJjbHVzdGVyIikgI2ZvciBzaWxob3VldHRlIHNjb3JlCkJpb2NNYW5hZ2VyOjppbnN0YWxsKCJmcGMiKSAjZm9yIGNoIGluZGV4CkJpb2NNYW5hZ2VyOjppbnN0YWxsKCdjbHYnKSAjZm9yIGRiaQoKQmlvY01hbmFnZXI6Omluc3RhbGwoJ1NldXJhdCcpICNmb3IgcGNhIGFuZCBmbG93c29tIHZpc3VhbGl6YXRpb24KYGBgCgpgYGB7cn0KbGlicmFyeShGbG93U09NKQpsaWJyYXJ5KGZsb3dDb3JlKQpsaWJyYXJ5KGNsdXN0ZXIpCmxpYnJhcnkoZnBjKQpsaWJyYXJ5KGNsdikKbGlicmFyeShTZXVyYXQpCmxpYnJhcnkoZHBseXIpCnJtKGxpc3Q9bHMoKSkKCmBgYAoKCmBgYHtyfQoKI2lucHV0IGZpbGUgcGF0aCwgY2hhbmdlIGlmIG5lZWRlZApmaWxlTmFtZSA8LSIvVXNlcnMvcmhhbGVuYXRob21hcy9Eb2N1bWVudHMvRGF0YS9GbG93Q3l0b21ldHJ5L1BoZW5vSUQvQW5hbHlzaXMvOU1CTy9wcmVwcm9fb3V0c2phbjIwLTkwMDBjZWxscy9wcmVwcm9fb3V0c2Zsb3dzZXQuY3N2IgoKIyBub3RlOiBjdXJyZW50IG1hdHJpeCBzYW1wbGUgSUQgaGF2ZSBjZWxsIGluZGV4ICMgYXR0YWNoZWQuIAoKZGYgPC0gcmVhZC5jc3YoZmlsZU5hbWUpCmhlYWQoZGYpCnByaW50KGRpbShkZikpICMgdGhpcyBpcyBzcGVjaWZpYyBkZiBoYXMgNzM1NzggY2VsbHMKIyB0aGUgcHJlcHJvY2Vzc2luZyBvdXRwdXQgY3N2IG5lZWRzIHRvIGJlIGNsZWFuZWQgLSBpdCBjb250YWlucyBsaXZlIGRlYWQsIEZTQywgU1NDIGFuZCB0aGUgc2FtcGxlIGNvbHVtbgpkZjIgPC0gZGYgJT4lIHNlbGVjdCgtYygiTGl2ZS5EZWFkIixGU0MsU1NDLFgsQmF0Y2gsY2VsbCkpCgptIDwtIGFzLm1hdHJpeChkZjIpICMgZmxvd3NldCB0YWtlcyBpbmEgbWF0cml4IG5vdCBkZiAKI20gPC0gb21bMTozMDAwLF0gI3N1YnNldCAobj0zMDAwKSwgb21pdCB0aGlzIHRvIHRlc3QgdGhlIHdob2xlIGZpbGUgIC0gSSBkaWRuJ3Qgc3Vic2V0IGhlcmUgYnV0IHRvbyA5MDAwIG9yIG1heCBjZWxscyBmcm9tIGVhY2ggZmlsZSBiZWZvcmUKCiN0cnkgc2NhbGluZwojIFNPTSA9IHNlbGYgb3JnYW5pemluZyBtYXAsIE1TVCA9IG1pbmltYWwgc3Bhbm5pbmcgdHJlZQoKIyBpZiByZWFkaW5nIGluIGEgY3N2IGNvbnZlcnQgdG8gZmxvd3NldApmcmFtZSA8LSBuZXcoImZsb3dGcmFtZSIsIGV4cHJzID0gbSkgI2NvbnZlcnQgaW5wdXQgdG8gZmxvd2ZyYW1lCmZzIDwtIFJlYWRJbnB1dChmcmFtZSkgI2NvbnZlcnQgZmxvd2ZyYW1lIHRvIGZsb3dzb20gb2JqZWN0CiMgZnMgPC0gQnVpbGRTT00oZnMsY29sc1RvVXNlPSgtMSkpICMtMSBiZWNhdXNlIHdlIGFyZSBub3QgdXNpbmcgIlgiIGNvbHVtbiB0byBidWlsZCBTT00KZnMgPC0gQnVpbGRTT00oZnMpICMgYnVpbGQgZmxvd1NPTSBvYmplY3QsIG5vIG5lZWQgZm9yIC0xIGJlY2F1c2UgSSBjbGVhbmVkIHRoZSBkZiBhYm91dCBiZWZvcmUgbWFraW5nIGZsb3dzZXQgCmZzIDwtIEJ1aWxkTVNUKGZzKSAjIGJ1aWxkIG1pbmltdW0gc3Bhbm5pbmcgdHJlZSAKIyBCdWlsZE1TVChmbG93U09NIG9iamVjdCBnZW5lcmF0ZWQgYnkgYnVpbGRTT00pCgojIHNhdmUgdGhlIEZsb3dTT00gTVNUIG9iamVjdCB0byB0cnkgYW5kIGF2b2lkIG1lbW9yeSBlcnJvcgpzYXZlUkRTKGZzLCIvVXNlcnMvcmhhbGVuYXRob21hcy9Eb2N1bWVudHMvRGF0YS9GbG93Q3l0b21ldHJ5L1BoZW5vSUQvQW5hbHlzaXMvOU1CTy9wcmVwcm9fb3V0c2phbjIwLTkwMDBjZWxscy9GbG93U09NaW4ucmRzIikKCgpgYGAKClNhdmVkIHRoZSBmbG93c2V0IG9iamVjdCBJIGNyZWF0ZWQgYW5kIGxvYWQgdGhlIG9iamVjdCBoZXJlIC0gYnV0IEkgZGlkbid0IHNhdmUgdGhlIGZpbHRlcmVkIGRmIGNvbnZlcnRlZCB0byBtYXRyaXgsIHdoaWNoIGlzIG5lZWRlZCBmb3IgdGhlIHN0YXRzIGJlbG93CgpgYGB7cn0KZnMgPC0gcmVhZFJEUygiL1VzZXJzL3JoYWxlbmF0aG9tYXMvRG9jdW1lbnRzL0RhdGEvRmxvd0N5dG9tZXRyeS9QaGVub0lEL0FuYWx5c2lzLzlNQk8vcHJlcHJvX291dHNqYW4yMC05MDAwY2VsbHMvRmxvd1NPTWluLnJkcyIpCgojIG5vdGUgdG8gU2h1bWluZyAtIHdlIHNob3VsZCBiZSBhYmxlIHRvIGdvIGRpcmVjdGx5IGZyb20gZmxvd3NldCBpbiBwcmVwcm9jZXNzaW5nIGludG8gdGhpcyBmbG93c2V0IGJ1dCB0aGVuIHRoZSA5IHNhbXBsZXMgKG9yIGhvd2V2ZXIgbWFueSB3aWxsIGJlIGluIHNlcGFyYXRlIGZyYW1lcykKIyBJIGRvbid0IGtub3cgd2hlcmUgdGhlIGFjdHVhbCB2YWx1ZXMgYXJlIGxvY2F0ZWQKCmBgYAoKCgpgYGB7cn0KIyBza2lwCiNiYXNlZCBvbiBmbG93c29tLCB0aGUgb3B0aW1hbCBrIG51bWJlciBvZiBjbHVzdGVycyA9IDUgZm9yIHRoZSBzYW1wbGUgKG49MzAwMCkgCm1ldGFjbCA8LSBNZXRhQ2x1c3RlcmluZyhmcyRtYXAkY29kZXMsCiJtZXRhQ2x1c3RlcmluZ19jb25zZW5zdXMiLAptYXggPSAxNSkKCnVuaXF1ZShtZXRhY2xbZnMkbWFwJG1hcHBpbmdbLCAxXV0pCmBgYAoKCgoKCmBgYHtyfQoKI2EgZnVuY3Rpb24gdGhhdCBjYWxjdWxhdGUgMyBzdGF0cyBmb3IgayBudW1iZXIgb2YgY2x1c3RlcmluZwojIHRoZSBzaWxob3VldHRlIHN0YXQgdGFrZXMgdG9vIG11Y2ggbWVtb3J5IGFuZCB3ZSB3aWxsIG5vdCBydW4gdGhpcyBmb3Igbm93LgoKIyBTaHVtaW5nIEkgbmVlZCB0aGlzIGZ1bmN0aW9uIHRvIHRha2UgaW4gYSBsaXN0IG9mIGNsdXN0ZXJpbmcgaW5kZXhlcyBhbmQgYW4gZXhwcmVzc2lvbiBtYXRyaXgKCnN0YXRzIDwtIGZ1bmN0aW9uKGtyYW5nZSl7CiAjIHNpX2xpIDwtbGlzdCgpCiAgY2hfbGkgPC1saXN0KCkKICBkYmlfbGkgPC1saXN0KCkKICAKICBmb3IgKGsgaW4ga3JhbmdlKSB7CiAgICAKICAgICNmbG93c29tIGNsdXN0ZXJpbmcsIHRyeSBlYWNoIGsgaW4ga3JhbmdlCiAgICBtZXRhQ2x1c3RlcmluZyA8LSAobWV0YUNsdXN0ZXJpbmdfY29uc2Vuc3VzKGZzJG1hcCRjb2RlcyxrID0gayxzZWVkPTQyKSkgIyBoZXJlIHdlIGFyZSBjbHVzdGVyaW5nIHdpdGhpbiB0aGUgbG9vcAogICAgIyBAU2h1bWluZyB3ZSBuZWVkIHRvIGNoYW5nZSB0aGlzIHNvIHRoYXQgdGhlIGxvb3AgY2FuIHB1bGwgdGhlIHJlc3VsdHMgb2YgY2x1c3RlcmluZyBvdXQgb2YgYW5vdGhlciBkYXRhIG9iamVjdAogICAgIyBob3cgYW0gSSBnb2luZyB0byB0ZXN0IHRoZSBTZXVyYXQgb3IgUGhlbm9ncmFwaCBjbHVzdGVyaW5nPwoKICAgICNjYWxjdWxhdGUgc2lsaG91ZXR0ZSBzY29yZQogICAgIyBjbHVzdGVyIGluZGV4LCBkaXN0YW5jZSBtYXRyaXggb2YgcGFpcnMgLS0tICBtYXliZSBjYWxjdWxhdGUgaW4gYWR2YW5jZSAKICAgICNzaSA8LSBzaWxob3VldHRlKG1ldGFDbHVzdGVyaW5nW2ZzJG1hcCRtYXBwaW5nWywgMV1dLGRpc3QobSksKQoKICAgICNzaV9saVtrXSA8LSBtZWFuKHNpWywgM10pCiAgICAKICAgICNjYWxjdWxhdGUgQ2FsaW5za2ktSGFyYWJhc3ogaW5kZXgKICAgICMgY2FsaW5oYXJhKHgsY2x1c3RlcmluZyxjbj1tYXgoY2x1c3RlcmluZykpCiAgICAjIHdoZXJlIHggaXMgYSBtYXRyaXggb3IgZGF0YWZyYW1lCiAgICBjaCA9IGNhbGluaGFyYShtLG1ldGFDbHVzdGVyaW5nW2ZzJG1hcCRtYXBwaW5nWywgMV1dLGNuPW1heChtZXRhQ2x1c3RlcmluZ1tmcyRtYXAkbWFwcGluZ1ssMV1dKSkKICAgIAogICAgY2hfbGlba10gPC0gY2gKICAgIAogICAgI2NhbGN1bGF0ZSBEYXZpZXPigJNCb3VsZGluIGluZGV4CiAgICBkYmkgPSBjbHYuRGF2aWVzLkJvdWxkaW4oY2xzLnNjYXR0LmRhdGEoCiAgICAgIG0sCiAgICAgIG1ldGFDbHVzdGVyaW5nW2ZzJG1hcCRtYXBwaW5nWywgMV1dKSwKICAgICAgaW50cmFjbHMgPSAiYXZlcmFnZSIsCiAgICAgIGludGVyY2xzID0gImF2ZXJhZ2UiCiAgICApCgogICAgZGJpX2xpW2tdIDwtIGRiaVsxXQogICAgfSAKCiAgcmV0dXJuKGxpc3QoY2hfbGksIGRiaV9saSkpCn0KCgoKYGBgCgoKYGBge3J9CiMgcnVuIHRoZSBmdW5jdGlvbiB0byBnZXQgdGhlIHN0YXRzCgoKCmtyYW5nZSA8LSAzOjE1ICNyYW5nZSBvZiBudW1iZXIgb2YgY2x1c3RlcnMKbGkgPSBzdGF0cyhrcmFuZ2UpICAjIGRvZXNuJ3QgY29udGFpbiBzaWxob3VldHRlIAojIGxpc3Qgb2YgMiAKCiNzaWxob3VldHRlIHNjb3JlOiByYW5nZXMgZnJvbSAtMSAgdG8gMSAKIy0xOiBiYWQgY2x1c3RlcnMgIDA6IG5ldXRyYWwsIGluZGlmZmVyZW50ICAxOiBnb29kIGNsdXN0ZXJzCiNwbG90KGtyYW5nZSwgdHlwZT0nYicsIGxpW1sxXV1ba3JhbmdlXSwgeGxhYj0nTnVtYmVyIG9mIGNsdXN0ZXJzJywgeWxhYj0nQXZlcmFnZSBTaWxob3VldHRlIFNjb3JlcycsIGZyYW1lPVRSVUUpCgojQ2FsaW5za2ktSGFyYWJhc3ogaW5kZXg6IAojIHRoZSBoaWdoZXN0IHZhbHVlIGlzIHRoZSBvcHRpbWFsIG51bWJlciBvZiBjbHVzdGVycwpwbG90KGtyYW5nZSwgdHlwZT0nYicsIGxpW1sxXV1ba3JhbmdlXSwgeGxhYj0nTnVtYmVyIG9mIGNsdXN0ZXJzJywgeWxhYj0nQ2FsaW5za2ktSGFyYWJhc3ogaW5kZXgnLCBmcmFtZT1UUlVFKQoKI0Rhdmllc+KAk0JvdWxkaW4gaW5kZXg6IG1pbmltdW0gc2NvcmUgaXMgemVybwojdGhlIGxvd2VzdCB2YWx1ZSBpcyB0aGUgb3B0aW1hbCBudW1iZXIgb2YgY2x1c3RlcnMKcGxvdChrcmFuZ2UsIHR5cGU9J2InLCBsaVtbMl1dW2tyYW5nZV0sIHhsYWI9J051bWJlciBvZiBjbHVzdGVycycsIHlsYWI9J0Rhdmllc+KAk0JvdWxkaW4gaW5kZXgnLCBmcmFtZT1UUlVFKQoKCmBgYApUaGUgQ2FsaW5za2ktSGFyYWJhc3ogaW5kZXggc2hvdyB0aGF0IEs9OCBpcyBiZXN0IG51bWJlciBvZiBjbHVzdGVycy4gIEJ1dCB0aGUgRGF2aWVz4oCTQm91bGRpbiBpbmRleCBzaG93cyBsb3dlciBpcyBiZXR0ZXIuICBJdCBpcyBva2F5IHVwIHVudGlsIDkuIAoKClRyeSBzdGF0aXN0aWNzIG9uIG90aGVyIGNsdXN0ZXJpbmcuICAKCgpgYGB7cn0KIyBmcm9tIFBoZW5vZ3JhcGggCiMgcmVhZCBpbiB0aGUgZGYgd2l0aCBwaGVub2dyYXBoIGNsdXN0ZXJzIGFuZCBzYXZlZCBleHByZXNzaW9uIG1hdHJpeCBvdXRwdXQgZnJvbSBwcmVwcm9jZXNzaW5nIC0gbm8gYWxpZ25lbWVudCBvciBub3JtYWxpemF0aW9uCnByaW50KGNvbG5hbWVzKGRmKSkKYWxsQUIgPC0gYygiQVFQNCIsIkNENTYiLCJHTEFTVCIsICJDRDE0MGEiLCJDRDI5IiwgIkNENDQiLCAiQ0QxODQiLCJDRDcxIiwiQ0QyNCIsIkNEMTUiLCJPNCIsIkhlcGFDQU0iLCJDRDEzMyIpCiMgd2UgbmVlZCBqdXN0IHRoZSBleHByZXNzaW9uIG1hdHJpeCAKZGYyIDwtIGRmICU+JSBzZWxlY3QoYygiQVFQNCIsIkNENTYiLCJHTEFTVCIsICJDRDE0MGEiLCJDRDI5IiwgIkNENDQiLCAiQ0QxODQiLCJDRDcxIiwiQ0QyNCIsIkNEMTUiLCJPNCIsIkhlcGFDQU0iLCJDRDEzMyIpKQoKbSA8LSBhcy5tYXRyaXgoZGYyKQoKCiNjYWxjdWxhdGUgQ2FsaW5za2ktSGFyYWJhc3ogaW5kZXgKICAgICMgY2FsaW5oYXJhKHgsY2x1c3RlcmluZyxjbj1tYXgoY2x1c3RlcmluZykpCiAgICAjIHdoZXJlIHggaXMgYSBtYXRyaXggb3IgZGF0YWZyYW1lCmNoLnBoZW5vLmsuMjcxID0gY2FsaW5oYXJhKG0sZGYkcGhlbm9ncmFwaF9jbHVzdGVyazI3MSxsZW5ndGgoKHVuaXF1ZShkZiRwaGVub2dyYXBoX2NsdXN0ZXJrMjcxKSkpKQoKY2gucGhlbm8uay41MCA9IGNhbGluaGFyYShtLGRmJHBoZW5vZ3JhcGhfY2x1c3RlcixsZW5ndGgoKHVuaXF1ZShkZiRwaGVub2dyYXBoX2NsdXN0ZXIpKSkpICAgCgojIHRoaXMgd29ya3Mgd2VsbCB0byBzZWUgdGhlIHN0YXRpc3RpY3MgYnV0IG5vdCBncmVhdCBmb3IgYSBwbG90LgojIEkgbWlnaHQgbmVlZCB0byBqdXN0IG1ha2UgYSB0YWJlbAoKCgoKYGBgCgpEYXZpZCBCb3VsZGluIGluZGV4IGZvciBQaGVub2dyYXBoIAoKYGBge3J9CiNjYWxjdWxhdGUgRGF2aWVz4oCTQm91bGRpbiBpbmRleAojIGluZGlleC5saXN0LCBpbnRyYWNscywgaW50ZXJjbHMgCiMgSSdtIG5vdCBzdXJlIGhvdyB0aGlzIGlzIHdvcmtpbmcgYnV0IGl0IGRvZXMgcnVuCmRiaS5waGVuLmsuMjcxIDwtIGNsdi5EYXZpZXMuQm91bGRpbihjbHMuc2NhdHQuZGF0YSgKICAgICAgbSwKICAgICAgZGYkcGhlbm9ncmFwaF9jbHVzdGVyazI3MSksCiAgICAgIGludHJhY2xzID0gImF2ZXJhZ2UiLAogICAgICBpbnRlcmNscyA9ICJhdmVyYWdlIgogICAgKQogIApkYmkucGhlbi5rLjUwIDwtIGNsdi5EYXZpZXMuQm91bGRpbihjbHMuc2NhdHQuZGF0YSgKICAgICAgbSwKICAgICAgZGYkcGhlbm9ncmFwaF9jbHVzdGVyKSwKICAgICAgaW50cmFjbHMgPSAiYXZlcmFnZSIsCiAgICAgIGludGVyY2xzID0gImF2ZXJhZ2UiCiAgICApCgoKYGBgCgoKbWFrZSB0aGlzIGNhbGN1bGF0aW9ucyBvbiB0aGUgU2V1cmF0IGNsdXN0ZXJpbmcKCgpgYGB7cn0KIyByZWFkIGluIHRoZSBzZXVyYXQgb2JqZWN0IHdpdGggdGhlIGNsdXN0ZXJpbmcgdmFsdWVzCnNldSA8LSByZWFkUkRTKCIvVXNlcnMvcmhhbGVuYXRob21hcy9Eb2N1bWVudHMvRGF0YS9GbG93Q3l0b21ldHJ5L1BoZW5vSUQvQW5hbHlzaXMvOU1CTy9wcmVwcm9fb3V0c2phbjIwLTkwMDBjZWxscy9TZXVyYXRmcm9tRmxvd3NvbVBoZW5vLnJkcyIgKQoKCmBgYAoKQ0ggaW5kZXggYW5kIERCSSBmcm9tIFNldXJhdCAoTG91dmFpbikgdHJ5IG9uZSByZXNvbHV0aW9uIGZpcnN0CgpgYGB7cn0KCmNoLmxvdXYucmVzLjEgPSBjYWxpbmhhcmEobSxzZXVAbWV0YS5kYXRhJFJOQV9zbm5fcmVzLjEsbGVuZ3RoKCh1bmlxdWUoc2V1QG1ldGEuZGF0YSRSTkFfc25uX3Jlcy4xKSkpKQojIHRoaXMgd29ya3MKCmRiaS5sb3V2LnJlcy4xIDwtIGNsdi5EYXZpZXMuQm91bGRpbihjbHMuc2NhdHQuZGF0YSgKICAgICAgbSwKICAgICAgc2V1QG1ldGEuZGF0YSRSTkFfc25uX3Jlcy4xKSwKICAgICAgaW50cmFjbHMgPSAiYXZlcmFnZSIsCiAgICAgIGludGVyY2xzID0gImF2ZXJhZ2UiCiAgICApCiMgdGhpcyBkb2Vzbid0IHdvcmsgLSBub3Qgc3VyZSB3aGVyZSB0byBzcGVjaWZ5IGhvdyB0byBmaW5kIHRoZSBkYXRhIHRvIGNhbGN1bGF0ZSBpbnRyYWNscyBhbmQgaW50ZXJjbHMKCgpgYGAKCkNhbGN1bGF0ZSBhbGwgQ0hJIGZvciBMb3V2YWluCgpgYGB7cn0KCiAgY2hfbG91diA8LWxpc3QoKQpsLnJhbmdlIDwtIGMoIlJOQV9zbm5fcmVzLjAuMSIsIlJOQV9zbm5fcmVzLjAuMjUiLCJSTkFfc25uX3Jlcy4wLjUiLCJSTkFfc25uX3Jlcy4wLjc1IiwiUk5BX3Nubl9yZXMuMSIpCnJlc29sdXRpb24gPSBjKDAuMSwwLjI1LDAuNSwwLjc1LDEpCnByZWZpeCA9ICJSTkFfc25uX3Jlcy4iIAojIGxvb3AgZG9lc24ndCB3b3JrIGJlY2F1c2UgSSBjYW4ndCBpbmRleCB0aGUgc2V1cmF0IHJlc29sdXRpb25zCgpjaCA9IGNhbGluaGFyYShtLHNldUBtZXRhLmRhdGEkUk5BX3Nubl9yZXMuMC4xLGxlbmd0aCgodW5pcXVlKHNldUBtZXRhLmRhdGEkUk5BX3Nubl9yZXMuMC4xKSkpKQpjaF9sb3V2WyJSTkFfc25uX3Jlcy4wLjEiXSA8LSBjaAoKY2ggPSBjYWxpbmhhcmEobSxzZXVAbWV0YS5kYXRhJFJOQV9zbm5fcmVzLjAuMjUsbGVuZ3RoKCh1bmlxdWUoc2V1QG1ldGEuZGF0YSRSTkFfc25uX3Jlcy4wLjI1KSkpKQpjaF9sb3V2WyJSTkFfc25uX3Jlcy4wLjI1Il0gPC0gY2gKCmNoID0gY2FsaW5oYXJhKG0sc2V1QG1ldGEuZGF0YSRSTkFfc25uX3Jlcy4wLjUsbGVuZ3RoKCh1bmlxdWUoc2V1QG1ldGEuZGF0YSRSTkFfc25uX3Jlcy4wLjUpKSkpCmNoX2xvdXZbIlJOQV9zbm5fcmVzLjAuNSJdIDwtIGNoCgpjaCA9IGNhbGluaGFyYShtLHNldUBtZXRhLmRhdGEkUk5BX3Nubl9yZXMuMC43NSxsZW5ndGgoKHVuaXF1ZShzZXVAbWV0YS5kYXRhJFJOQV9zbm5fcmVzLjAuNzUpKSkpCmNoX2xvdXZbIlJOQV9zbm5fcmVzLjAuNzUiXSA8LSBjaAoKY2ggPSBjYWxpbmhhcmEobSxzZXVAbWV0YS5kYXRhJFJOQV9zbm5fcmVzLjEsbGVuZ3RoKCh1bmlxdWUoc2V1QG1ldGEuZGF0YSRSTkFfc25uX3Jlcy4xKSkpKQpjaF9sb3V2WyJSTkFfc25uX3Jlcy4xIl0gPC0gY2gKCgoKCmBgYAoKUGxvdCB0aGUgQ0hJIGZyb20gTG91dmFpbiBjbHVzdGVycwpgYGB7cn0KCiMgdGhlIGhpZ2hlc3QgdmFsdWUgaXMgdGhlIG9wdGltYWwgbnVtYmVyIG9mIGNsdXN0ZXJzCnkgPC0gc2FwcGx5KGNoX2xvdXYsICdbJywgKQpwbG90KHJlc29sdXRpb24sIHksIHhsYWI9J1Jlc29sdXRpb24gb2YgY2x1c3RlcnMnLHR5cGUgPSAiYiIsIHlsYWI9J0NhbGluc2tpLUhhcmFiYXN6IGluZGV4JywgZnJhbWU9VFJVRSkKCgoKCmBgYAoKTWFrZSBhIHRhYmxlIHdpdGggdGhlIENISSBvdXRwdXRzCgpgYGB7cn0KCkNsdXN0ZXIudHlwZSA8LSBjKCJQaGVub2dyYXBoLmsuNTAiLCJQaGVub2dyYXBoLmsuMjcxIixsLnJhbmdlKQpwcmludChDbHVzdGVyLnR5cGUpCk1ldGhvZCA8LSBjKCJQaGVub2dyYXBoLmsuNTAiICwgIlBoZW5vZ3JhcGguay4yNzEiICwiUk5BX3Nubl9yZXMuMC4xIiwgICJSTkFfc25uX3Jlcy4wLjI1IiAsIlJOQV9zbm5fcmVzLjAuNSIgLCAiUk5BX3Nubl9yZXMuMC43NSIsICJSTkFfc25uX3Jlcy4xIikKCnByaW50KHkpCgpDSEkudmFsdWUgPC0gYyg3NDE4LjI5OSw3ODA2LjEzMCwgNTI1MjEuMTIwLCAgOTkzMC4yMzUsICA3NzM0LjA2NSwgIDQ5NDkuNTkzLCAgNDIyOS4yNTIpCgoKCmRmLmNoaSA8LSBjYmluZC5kYXRhLmZyYW1lKE1ldGhvZCwgQ0hJLnZhbHVlKQoKCgoKYGBgCgpNYWtlIGFub3RoZXIgcGxvdAoKYGBge3J9CiNsaWJyYXJ5KGdncGxvdDIpCmdncGxvdChkZi5jaGksIGFlcyh4PSBNZXRob2QsIHk9IENISS52YWx1ZSkpICsgZ2VvbV9wb2ludCgpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwKSkKCiMgaG93IGRvIHRoZXNlIHZhbHVlcyBjb21wYXJlIHdpdGggCgpgYGAKCgoKClBhcnQgMjogcGxvdAoKYGBge3J9CgojdHJhbnNwb3NlIHRoZSBjc3Ygc28gdGhhdCBzZXVyYXQgb2JqZWN0IGhhcyB0aGUgcmlnaHQgY29sdW1uIGFuZCByb3cKIyB0aGUgZmxvdyBpbnRlbnNpdHkgdmFsdWVzIHdpbGwgYmUgaW5wdXQgYXMgUk5BIGV4cHJlc3Npb24KdG0gPC0gdChkZjIpCnJvd25hbWVzKHRtKSA8LSBjb2xuYW1lcyhkZjIpCmNvbG5hbWVzKHRtKSA8LSByb3duYW1lcyhkZjIpCgoKayA8LSAxMCAjY2hhbmdlIG51bWJlciBvZiBjbHVzdGVyIGhlcmUgCmsgPC0gOCAjIGJlc3QgbnVtYmVyIG9mIGNsdXN0ZXJzIGFjY29yZGluZyB0byBDSCBpbmRleAoKbWV0YUNsdXN0ZXJpbmcgPC0gKG1ldGFDbHVzdGVyaW5nX2NvbnNlbnN1cyhmcyRtYXAkY29kZXMsayA9IGssc2VlZD00MikpICMgZmxvd1NPTSBjbHVzdGVyaW5nPwoKc2V1IDwtIENyZWF0ZVNldXJhdE9iamVjdCh0bSkgIyBjcmVhdGUgYSBzZXVyYXQgb2JqZWN0IAojIGNoZWNrIHRoZSBzZXVyYXQgb2JqZWN0IGJ5IHBsb3R0aW5nIHNvbWUgZmVhdHVyZXMKcHJpbnQoY29sbmFtZXMoZGYyKSkKYWxsQUIgPC0gY29sbmFtZXMoZGYyKQpWbG5QbG90KHNldSxmZWF0dXJlcyA9IGMoIkNENTYiLCJBUVA0IikpICMgZWVycm9yIGluIHBsb3QKRG90UGxvdChzZXUsIGZlYXR1cmVzID0gYygiQ0Q1NiIsIkFRUDQiLCJDRDI0IiwiR0xBU1QiLCJDRDE0MGEiLCJDRDI5IiwiQ0QxODQiLCJDRDcxIiwiTzQiLCJIZXBhQ0FNIiwiQ0QxMzMiKSkKIyBzY2FsZSB0aGUgZGF0YSAKc2V1IDwtIFNjYWxlRGF0YShzZXUpCgpEb3RQbG90KHNldSwgZmVhdHVyZXMgPSBjKCJDRDU2IiwiQVFQNCIsIkNEMjQiLCJHTEFTVCIsIkNEMTQwYSIsIkNEMjkiLCJDRDE4NCIsIkNENzEiLCJPNCIsIkhlcGFDQU0iLCJDRDEzMyIpKQpEb3RQbG90KHNldSwgZmVhdHVyZXMgPSBhbGxBQikKI2RvIGkgbmVlZCB0byBub3JtYWxpemU/CiMgc2V1IDwtIE5vcm1hbGl6ZURhdGEoc2V1LCBub3JtYWxpemF0aW9uLm1ldGhvZCA9ICJMb2dOb3JtYWxpemUiLCBzY2FsZS5mYWN0b3IgPSAxMDAwMCkKCiMgY3JlYXRlIGEgVU1BUAojIHRvIGRvIHNvIFBDQSBtdXN0IGJlIHJ1biBmaXJzdCAKc2V1IDwtIEZpbmRWYXJpYWJsZUZlYXR1cmVzKHNldSkgIyBuZWVkZWQgZm9yIFBDQSBvciBzZWxlY3QgYWxsIGZlYXR1cmVzCiNkaW1lbnRpb25hbGl0eSByZWR1Y3Rpb24gbWV0aG9kczogcGNhIGFuZCB1bWFwCgpzZXUgPC1SdW5QQ0Eoc2V1LHNlZWQudXNlID0gNDIpICMgd2lsbCB1c2UgdmFyaWFibGUgZmVhdHVyZXMgYnkgZGVmYXVsdApzZXUgPC0gUnVuUENBKHNldSwgZmVhdHVyZXMgPSBhbGxBQikKCnByaW50KHNldVtbInBjYSJdXSwgZGltcyA9IDE6NSwgbmZlYXR1cmVzID0gNSkgIyB0ZWxscyB5b3Ugd2hhdCBBQiBhcmUgY29udHJpYnV0aW5nIHRvIHRoZSAKc2V1IDwtIFJ1blVNQVAoc2V1LGZlYXR1cmVzID0gYWxsQUIpCgojIEFkZCB0aGUgY2x1c3RlcmluZyBkYXRhIHRvIHRoZSBzZXVyYXQgb2JqZWN0CnNldSA8LSBBZGRNZXRhRGF0YShvYmplY3Q9c2V1LCBtZXRhZGF0YT1tZXRhQ2x1c3RlcmluZ1tmcyRtYXAkbWFwcGluZ1ssMV1dLCBjb2wubmFtZSA9ICdmbG93U09NLmsuOCcpCgpEaW1QbG90KHNldSwgZ3JvdXAuYnkgPSAiZmxvd1NPTSIsIHJlZHVjdGlvbiA9ICJwY2EiKSAKRGltUGxvdChzZXUsIGdyb3VwLmJ5ID0gImZsb3dTT00iLCByZWR1Y3Rpb24gPSAidW1hcCIpICMgZnJvbSBub3JtYWwgUENBIHVzZWQgYXMgdW1hcCBpbnB1dCBpdCBpcyBtb3N0bHkgb24gYmxvYiwgSSBnZXQgdGhlIHNhbWUgc2hhcGUgd2hlbiB1c2luZyBhbGwgQUIgZmVhdHVyZXMgYXMgUENBIGlucHV0CgpEb3RQbG90IChzZXUsIGZlYXR1cmVzID0gYWxsQUIsIGdyb3VwLmJ5ID0gImZsb3dTT00iKQoKCiNhbGxvdyBmb3IgY29sb3IgbGFiZWxpbmcKSWRlbnRzKHNldSkgPC0gImZsb3dTT00iCmBgYAoKRkxvd1NPTSBjbHVzdGVyaW5nIHZpc3VhbGx5IGFwcGVhciB0byBiZSB0ZXJyaWJsZS4KCkknbGwganVzdCB0cnkgdGhlIHNldXJhdCBjbHVzdGVyaW5nCgpgYGB7cn0KCiMgY2x1c3RlciB1c2luZyBMb3V2YWluIGNsdXN0ZXJpbmcKIyB1c2luZyB0aGUgc3F1YXJlIHJvb3Qgb2YgdGhlIG51bWJlciBvZiBjZWxscyBmb3IgSyBtaWdodCB3b3JrIGJldHRlcgojc3FydCg3MzU3OCkgPSAyNzEuMjUKc2V1IDwtIEZpbmROZWlnaGJvcnMoc2V1LCBkaW1zID0gMToxMCwgayA9IDI3MSkKc2V1IDwtIEZpbmRDbHVzdGVycyhzZXUsIHJlc29sdXRpb24gPSBjKDAuMSwwLjI1LDAuNSwwLjc1LDEpKQpjbHVzdHJlZShzZXUsIHByZWZpeCA9ICJSTkFfc25uX3Jlcy4iKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKQojIHRoaXMgaXMgIHZlcnkgc2xvdyAtIGRvIG5vdCBydW4gYWdhaW4gaW4gbm90ZWJvb2sKCmBgYAoKYGBge3J9CmxpYnJhcnkoY2x1c3RyZWUpCmNsdXN0cmVlKHNldSwgcHJlZml4ID0gIlJOQV9zbm5fcmVzLiIpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpCmBgYAoKYGBge3J9CiMgZnJvbSBDbHVzdHJlZSAwLjUgbG9va3MgbGlrZSB0aGUgYmVzdCByZXNvbHV0aW9uCgojIGMoMC4xLDAuMjUsMC41LDAuNzUsMSkKcmVzb2x1dGlvbnMgPSBjKCJSTkFfc25uX3Jlcy4wLjEiLCJSTkFfc25uX3Jlcy4wLjI1IiwiUk5BX3Nubl9yZXMuMC41IiwiUk5BX3Nubl9yZXMuMC43NSIsIlJOQV9zbm5fcmVzLjEiKQoKZm9yIChyZXMgaW4gcmVzb2x1dGlvbnMpewogcHJpbnQoRGltUGxvdChzZXUsIHJlZHVjdGlvbiA9ICJ1bWFwIiwgcmVwZWwgPSBUUlVFLCBsYWJlbCA9IFRSVUUsIGdyb3VwLmJ5ID0gcmVzKSkKIAp9CgoKYGBgCgpUaGUgY2x1c3RlcnMgYXJlIG5vdCB3ZWxsIHNlcGFyYXRlZCwgYnV0IGJldHRlciB0aGFuIHdpdGggZmxvd1NPTSAodmlzdWFsbHkpCkxldHMgc2VlIGhvdyBhIGRvdHBsb3QgbG9va3MKCmBgYHtyfQpEb3RQbG90KHNldSwgZ3JvdXAuYnkgPSAiUk5BX3Nubl9yZXMuMC41IiwgZmVhdHVyZXMgPSBhbGxBQiwgc2NhbGUgPSBUUlVFKSAjIHdoeSBpcyB0aGUgaW50ZW5zaXR5IHBsb3RlZD8KIyB0aGUgZGF0YSBtdXN0IG5vdCBiZSB3b3JraW5nIGNvcnJlY3RseQoKCiMgbWF5YmUgdGhlIG9iamVjdCBpcyBub3QgcmVhbGx5IGNvcnJlY3QgYXQgYWxsCkZlYXR1cmVTY2F0dGVyKHNldSwgZmVhdHVyZTEgPSAiQ0Q1NiIsZmVhdHVyZTIgPSAiQ0QyNCIpCkZlYXR1cmVTY2F0dGVyKHNldSwgZmVhdHVyZTEgPSAiQ0QyOSIsZmVhdHVyZTIgPSAiQ0QxODQiKQoKIyBtYXliZSB0aGUgZGF0YSBuZWVkcyB0byBiZSBub3JtYWxpemVkCnNldSA8LSBOb3JtYWxpemVEYXRhKHNldSkKCkRvSGVhdG1hcChzZXUsIGZlYXR1cmVzID0gYWxsQUIsIGdyb3VwLmJ5ID0gIlJOQV9zbm5fcmVzLjAuNSIpCiMgdGhlIGdyb3VwcyBsb29rIG9rYXkgYnkgaGVhdG1hcC4gSSdsbCB0cnkgdGhlIGRpZmZlcmVudCByZXNvbHV0aW9ucyAKCgoKYGBgCgpgYGB7cn0KIyBjKDAuMSwwLjI1LDAuNSwwLjc1LDEpCnJlc29sdXRpb25zID0gYygiUk5BX3Nubl9yZXMuMC4xIiwiUk5BX3Nubl9yZXMuMC4yNSIsIlJOQV9zbm5fcmVzLjAuNSIsIlJOQV9zbm5fcmVzLjAuNzUiLCJSTkFfc25uX3Jlcy4xIikKCmZvciAocmVzIGluIHJlc29sdXRpb25zKXsKIHByaW50KERvSGVhdG1hcChzZXUsIGZlYXR1cmVzID0gYWxsQUIsIGdyb3VwLmJ5ID0gcmVzKSkKIAp9CgpgYGAKCmBgYHtyfQojIHRyeSB0aGUgZG90cGxvdCBhZ2FpbgojIGMoMC4xLDAuMjUsMC41LDAuNzUsMSkKcmVzb2x1dGlvbnMgPSBjKCJSTkFfc25uX3Jlcy4wLjEiLCJSTkFfc25uX3Jlcy4wLjI1IiwiUk5BX3Nubl9yZXMuMC41IiwiUk5BX3Nubl9yZXMuMC43NSIsIlJOQV9zbm5fcmVzLjEiKQoKZm9yIChyZXMgaW4gcmVzb2x1dGlvbnMpewogcHJpbnQoRG90UGxvdChzZXUsIGZlYXR1cmVzID0gYWxsQUIsIGdyb3VwLmJ5ID0gcmVzLCBjb2xzID0gYygiYmx1ZSIsInJlZCIpKSkKIAp9CgojIHdoYXQgaXMgaGFwcGVuaW5nPwoKYGBgCgpUcnkgZmVhdHVyZSBtYXBzCgpgYGB7cn0KCgpmb3IgKEFCIGluIGFsbEFCKXsKIHByaW50KEZlYXR1cmVQbG90KHNldSwgZmVhdHVyZXMgPSBBQiksc2xvdCA9ICdzY2FsZS5kYXRhJyxtaW4uY3V0b2ZmID0gJ3ExMCcsIG1heC5jdXRvZmYgPSc5MCcpCiAKfQoKIyBhdXRvdGhyZXNob2xkIGlzbid0IGdyZWF0IC0gSSBzZXQgcXVhcnRpbGVzIAoKI0ZlYXR1cmVQbG90KHNldSwgZmVhdHVyZXMgPSAiTzQiLCBtaW4uY3V0b2ZmID0gMSwgbWF4LmN1dG9mZiA9IDI1KQojRmVhdHVyZVBsb3Qoc2V1LCBmZWF0dXJlcyA9ICJPNCIsIHNsb3QgPSAnc2NhbGUuZGF0YScsbWluLmN1dG9mZiA9IDAuMSwgbWF4LmN1dG9mZiA9IDI1KQoKI0ZlYXR1cmVQbG90KHNldSwgZmVhdHVyZXMgPSAiTzQiLCBzbG90ID0gJ3NjYWxlLmRhdGEnLG1pbi5jdXRvZmYgPSAncTUnLCBtYXguY3V0b2ZmID0nOTknKQoKCgoKYGBgCgpUcnkgcmlkZ2UgcGxvdHMKCmBgYHtyfQpSaWRnZVBsb3Qoc2V1LCBmZWF0dXJlcyA9IGMoIkNENTYiLCJDRDI0IiksIG5jb2wgPSAyKSAjIG1pc3NpbmcgdmFsdWVzPz8/ClZsblBsb3Qoc2V1LCBmZWF0dXJlcyA9IGMoIkNENTYiLCJDRDI0IiksIG5jb2wgPSAyKSAjIGFsc28gbWlzc2luZyB2YWx1ZXM/Pz8KCmBgYAoKCkknbGwgc2F2ZSB0aGUgc2V1cmF0IG9iamVjdCBhbmQgc2VlIGFib3V0IHRyeWluZyB0byBjbHVzdGVyIHdpdGggcGhlbm9ncmFwaC4gIEkgYmVsaWV2ZSBJIGhhdmUgcmVhZCBpbiB0aGUgdW5hbGxpZ25lZCBub3Qgbm9ybWFsaXplZCBkYXRhIAoKCmBgYHtyfQpzYXZlUkRTKHNldSwgIi9Vc2Vycy9yaGFsZW5hdGhvbWFzL0RvY3VtZW50cy9EYXRhL0Zsb3dDeXRvbWV0cnkvUGhlbm9JRC9BbmFseXNpcy85TUJPL3ByZXByb19vdXRzamFuMjAtOTAwMGNlbGxzL1NldXJhdGZyb21GbG93c29tLnJkcyIpCgoKCmBgYAoKClBoZW5vZ3JhcGggY2x1c3RlcmluZyB1c2VzIEphY2NhcmQgY29lZmZpY2llbnQgYmV0d2VlbiBuZXJ1ZXN0IG5laWdib3JzIHNldHMgYW5kIHRoZW4gaWQgY29tbXVuaXRpZXMgYnkgbG91dmFpbiAKCgpgYGB7cn0KIyBpbnN0YWxsCmlmKCFyZXF1aXJlKGRldnRvb2xzKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygiZGV2dG9vbHMiKSAjIElmIG5vdCBhbHJlYWR5IGluc3RhbGxlZAp9CmRldnRvb2xzOjppbnN0YWxsX2dpdGh1YigiSmlubWlhb0NoZW5MYWIvUnBoZW5vZ3JhcGgiKQoKbGlicmFyeShScGhlbm9ncmFwaCkKCmBgYAoKCgoK